package com.example.cd5160866.urlapp.view;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.support.annotation.ColorInt;
import android.support.annotation.IntDef;
import android.support.annotation.IntRange;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.text.TextPaint;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.View;
import android.widget.FrameLayout;

import com.example.cd5160866.urlapp.R;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.LinkedList;
import java.util.List;

/**
 * @author cd5160866
 * @date 2018/8/24
 */

public class GroupCell extends FrameLayout {

    /**
     * Don't show any dividers.
     */
    public static final int SHOW_DIVIDER_NONE = 0;
    /**
     * Show a divider at the top of the view.
     */
    public static final int SHOW_DIVIDER_TOP = 1 << 1;
    /**
     * Show a divider at the bottom of the view.
     */
    public static final int SHOW_DIVIDER_BOTTOM = 2 << 1;

    /**
     * Show divider padding int the view  start and end when drawablePadding has set
     */
    public static final int DIVIDER_PADDING_GRAVITY_BOTH = 0;

    /**
     * Show divider padding int the view  start when drawablePadding has set
     */

    public static final int DIVIDER_PADDING_GRAVITY_START = 1 << 1;

    /**
     * Show divider padding int the view   end when drawablePadding has set
     */
    public static final int DIVIDER_PADDING_GRAVITY_END = 2 << 1;


    private static final String WRAP = "\n";

    @IntDef(flag = true,
            value = {
                    SHOW_DIVIDER_NONE,
                    SHOW_DIVIDER_TOP,
                    SHOW_DIVIDER_BOTTOM
            })
    @Retention(RetentionPolicy.SOURCE)
    private @interface DividerMode {
    }

    @IntDef(flag = true,
            value = {
                    DIVIDER_PADDING_GRAVITY_START,
                    DIVIDER_PADDING_GRAVITY_END,
                    DIVIDER_PADDING_GRAVITY_BOTH
            })
    @Retention(RetentionPolicy.SOURCE)
    private @interface DividerPaddingGravity {
    }

    private Drawable mDrawableStart;

    private int mDrawableStartHeight;

    private int mDrawableStartWidth;

    private Drawable mDrawableEnd;

    private int mDrawableEndWidth;

    private int mDrawableEndHeight;

    private int mDrawablePadding;

    private Drawable mDivider;

    private int mDividerTopPadding;

    private int mDividerBottomPadding;

    private CharSequence mText;

    private float mTextSize;

    private int mTextColor;

    private CharSequence mTextEnd;

    private float mTextSizeEnd;

    private int mTextColorEnd;

    private boolean mIsTextBold;

    private int mDividerHeight;

    private int mShowDividers;

    private int mDividerPaddingGravity;

    private int mMaxLines;

    private int mLineSpaceExtra;

    private TextPaint mTextPaint;

    public GroupCell(@NonNull Context context) {
        this(context, null);
    }

    public GroupCell(@NonNull Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        init(attrs);
    }

    private void init(@Nullable AttributeSet attrs) {

        TypedArray array = getContext().obtainStyledAttributes(attrs,
                R.styleable.GroupCell);
        //compoundDrawables
        mDrawableStart = array.getDrawable(R.styleable.GroupCell_android_drawableStart);
        mDrawableStartWidth = array.getDimensionPixelOffset(R.styleable.GroupCell_drawableStartWidth, null == mDrawableStart ? 0 : mDrawableStart.getIntrinsicWidth());
        mDrawableStartHeight = array.getDimensionPixelOffset(R.styleable.GroupCell_drawableStartHeight, null == mDrawableStart ? 0 : mDrawableStart.getIntrinsicHeight());
        mDrawableEnd = array.getDrawable(R.styleable.GroupCell_android_drawableEnd);
        mDrawableEndWidth = array.getDimensionPixelOffset(R.styleable.GroupCell_drawableEndWidth, null == mDrawableEnd ? 0 : mDrawableEnd.getIntrinsicWidth());
        mDrawableEndHeight = array.getDimensionPixelOffset(R.styleable.GroupCell_drawableEndHeight, null == mDrawableEnd ? 0 : mDrawableEnd.getIntrinsicHeight());
        mDrawableEndWidth = array.getDimensionPixelOffset(R.styleable.GroupCell_drawableEndWidth, 20);
        mDrawableEndHeight = array.getDimensionPixelOffset(R.styleable.GroupCell_drawableEndHeight, 20);
        mDrawablePadding = array.getDimensionPixelSize(R.styleable.GroupCell_android_drawablePadding, 0);
        //divider
        mShowDividers = array.getInt(R.styleable.GroupCell_showDivider, SHOW_DIVIDER_NONE);
        mDividerPaddingGravity = array.getInt(R.styleable.GroupCell_dividerPaddingGravity, DIVIDER_PADDING_GRAVITY_BOTH);
        mDividerTopPadding = array.getDimensionPixelOffset(R.styleable.GroupCell_dividerTopPadding, 0);
        mDividerBottomPadding = array.getDimensionPixelOffset(R.styleable.GroupCell_dividerBottomPadding, 0);
        mDividerHeight = array.getDimensionPixelOffset(R.styleable.GroupCell_android_dividerHeight, 0);
        setDividerDrawable(array.getDrawable(R.styleable.GroupCell_android_divider));
        //text
        mText = array.getText(R.styleable.GroupCell_android_text);
        mTextSize = array.getDimensionPixelSize(R.styleable.GroupCell_android_textSize, 24);
        mTextColor = array.getColor(R.styleable.GroupCell_android_textColor, Color.BLACK);
        mTextEnd = array.getString(R.styleable.GroupCell_textEnd);
        mTextSizeEnd = array.getDimensionPixelSize(R.styleable.GroupCell_textSizeEnd, 24);
        mTextColorEnd = array.getColor(R.styleable.GroupCell_textColorEnd, Color.BLACK);
        mIsTextBold = array.getBoolean(R.styleable.GroupCell_isTextBold, false);
        mMaxLines = array.getInt(R.styleable.GroupCell_android_maxLines, Integer.MAX_VALUE);
        mLineSpaceExtra = array.getDimensionPixelOffset(R.styleable.GroupCell_android_lineSpacingExtra, 0);
        array.recycle();
        initTextPaint();
        measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
    }

    /**
     * Set how dividers in this view
     *
     * @param showDividers One or more of {@link #SHOW_DIVIDER_TOP},
     *                     {@link #SHOW_DIVIDER_BOTTOM},
     *                     or {@link #SHOW_DIVIDER_NONE} to show no dividers.
     */
    public void setShowDividers(@DividerMode int showDividers) {
        if (showDividers != mShowDividers) {
            postInvalidate();
        }
        mShowDividers = showDividers;
    }

    /**
     * @param divider Set a drawable to be show.
     */
    public void setDividerDrawable(Drawable divider) {
        if (divider == mDivider) {
            return;
        }
        this.mDivider = divider;
        if (null != mDivider) {
            if (!(divider instanceof ColorDrawable)) {
                mDividerHeight = mDivider.getIntrinsicHeight();
            }
        } else {
            mDividerHeight = 0;
        }
        postInvalidate();
    }

    public void setDrawableStart(@Nullable Drawable start) {
        setDrawableStart(start, 0, 0);
    }

    /**
     * Set  Drawable  int the left, you can resize it if you need
     *
     * @param start  icon
     * @param width  drawable width
     * @param height drawable height
     */
    public void setDrawableStart(@Nullable Drawable start, int width, int height) {
        if (mDrawableStart == start) {
            return;
        }
        this.mDrawableStart = start;
        if (null != mDrawableStart) {
            boolean useParamsSize = width > 0 && height > 0;
            mDrawableStartWidth = useParamsSize ? width : mDrawableStart.getIntrinsicWidth();
            mDrawableStartHeight = useParamsSize ? height : mDrawableStart.getIntrinsicHeight();
        } else {
            mDrawableStartWidth = 0;
            mDrawableStartHeight = 0;
        }
        postInvalidate();
    }

    public void setDrawableEnd(@Nullable Drawable end) {
        setDrawableStart(end, 0, 0);
    }

    /**
     * Set  Drawable  int the right, you can resize it if you need
     *
     * @param end    icon
     * @param width  drawable width
     * @param height drawable height
     */
    public void setDrawableEnd(@Nullable Drawable end, int width, int height) {
        if (mDrawableEnd == end) {
            return;
        }
        this.mDrawableEnd = end;
        if (null != mDrawableEnd) {
            boolean useParamsSize = width > 0 && height > 0;
            mDrawableEndWidth = useParamsSize ? width : mDrawableEnd.getIntrinsicWidth();
            mDrawableEndHeight = useParamsSize ? height : mDrawableEnd.getIntrinsicHeight();
        } else {
            mDrawableEndWidth = 0;
            mDrawableEndHeight = 0;
        }
        postInvalidate();
    }

    /**
     * @param isBold Set text style bold
     */
    public void setTextBold(boolean isBold) {
        if (mIsTextBold == isBold) {
            return;
        }
        this.mIsTextBold = isBold;
        postInvalidate();
    }

    /**
     * @param drawablePadding Set padding between text and the drawables
     */
    public void setDrawablePadding(int drawablePadding) {
        if (mDrawablePadding == drawablePadding) {
            return;
        }
        this.mDrawablePadding = drawablePadding;
        postInvalidate();
    }

    /**
     * @param dividerTopPadding Set padding between view and the top divider
     */
    public void setDividerTopPadding(int dividerTopPadding) {
        if (mDividerTopPadding == dividerTopPadding) {
            return;
        }
        this.mDividerTopPadding = dividerTopPadding;
        postInvalidate();
    }

    /**
     * @param dividerBottomPadding Set padding between view and the bottom divider
     */
    public void setDividerBottomPadding(int dividerBottomPadding) {
        if (mDividerBottomPadding == dividerBottomPadding) {
            return;
        }
        this.mDividerBottomPadding = dividerBottomPadding;
        postInvalidate();
    }

    /**
     * @param gravity this method usually use with setDividerTopPadding or dividerBottomPadding,
     *                One or more of {@link #DIVIDER_PADDING_GRAVITY_BOTH}, make padding on divider start and divider end
     *                {@link #DIVIDER_PADDING_GRAVITY_START},
     *                or {@link #DIVIDER_PADDING_GRAVITY_END} to show padding on divider start or divider end.
     */
    public void setDividerPaddingGravity(@DividerPaddingGravity int gravity) {
        if (mDividerPaddingGravity != gravity) {
            postInvalidate();
        }
        mShowDividers = gravity;
    }

    /**
     * @param text Set text in the left
     */
    public void setText(CharSequence text) {
        if (TextUtils.isEmpty(text)) {
            text = "";
        }
        mText = text;
        requestLayout();
    }

    /**
     * @param color Set text color in the left
     */
    public void setTextColor(@ColorInt int color) {
        if (mTextColor == color) {
            return;
        }
        mTextColor = color;
        postInvalidate();
    }

    /**
     * @param textSize Set text size in the left
     */
    public void setTextSize(float textSize) {
        float trueSize = TypedValue.applyDimension(
                TypedValue.COMPLEX_UNIT_SP, textSize, getResources().getDisplayMetrics());
        if (mTextSize == trueSize) {
            return;
        }
        mTextSize = trueSize;
        requestLayout();
    }

    /**
     * @param text Set text in the right
     */
    public void setTextEnd(CharSequence text) {
        if (TextUtils.isEmpty(text)) {
            text = "";
        }
        mTextEnd = text;
        postInvalidate();
    }

    /**
     * @param color Set text color in the right
     */
    public void setTextColorEnd(@ColorInt int color) {
        if (mTextColorEnd == color) {
            return;
        }
        mTextColorEnd = color;
        postInvalidate();
    }

    /**
     * @param textSize Set text size in the right
     */
    public void setTextSizeEnd(float textSize) {
        float trueSize = TypedValue.applyDimension(
                TypedValue.COMPLEX_UNIT_SP, textSize, getResources().getDisplayMetrics());
        if (mTextSizeEnd == trueSize) {
            return;
        }
        mTextSizeEnd = trueSize;
        postInvalidate();
    }

    public void setMaxLines(@IntRange(from = 1, to = Integer.MAX_VALUE) int maxLines) {
        this.mMaxLines = maxLines;
        requestLayout();
        invalidate();
    }

    public void setLineSpacing(int add) {
        if (mLineSpaceExtra != add) {
            this.mLineSpaceExtra = add;
            requestLayout();
            invalidate();
        }
    }

    /**
     * get left text
     */

    public String getText() {
        if (null == mText) {
            return "";
        }
        return mText.toString();
    }

    /**
     * get right text
     */
    public String getTextEnd() {
        if (null == mTextEnd) {
            return "";
        }
        return mTextEnd.toString();
    }

    /**
     * @return left and right textSize
     */
    public float[] getTextSize() {
        float[] sizes = new float[2];
        sizes[0] = mTextSize;
        sizes[1] = mTextSizeEnd;
        return sizes;
    }

    /**
     * @return left and right textColor
     */
    @ColorInt
    public int[] getTextColor() {
        int[] colors = new int[2];
        colors[0] = mTextColor;
        colors[1] = mTextColorEnd;
        return colors;
    }


    /**
     * @return the divider Drawable in top and bottom.
     */
    public Drawable getDividerDrawable() {
        return mDivider;
    }

    /**
     * Returns drawables in the left and the right.
     */
    public Drawable[] getCompoundDrawables() {
        Drawable[] drawables = new Drawable[2];
        drawables[0] = mDrawableStart;
        drawables[1] = mDrawableEnd;
        return drawables;
    }

    /**
     * Returns the padding between the compound drawables and the text.
     */
    public int getCompoundDrawablePadding() {
        return mDrawablePadding;
    }

    /**
     * Returns the horizontal padding between the top divider and the container .
     */
    public int getDividerTopPadding() {
        return mDividerTopPadding;
    }

    /**
     * Returns the horizontal padding between the bottom divider and the container .
     */
    public int getDividerBottomTopPadding() {
        return mDividerBottomPadding;
    }

    @Override
    protected void dispatchDraw(Canvas canvas) {
        super.dispatchDraw(canvas);
        drawCellText(canvas);
        drawCellTextEnd(canvas);
        drawAllDrawables(canvas);
        drawDividers(canvas);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
        int finalWidth;
        int finalHeight;
        if (widthMode == MeasureSpec.EXACTLY) {
            finalWidth = widthSize;
        } else {
            finalWidth = getDefaultWidth(widthMeasureSpec, heightMeasureSpec);
        }
        if (heightMode == MeasureSpec.EXACTLY) {
            finalHeight = heightSize;
        } else {
            finalHeight = getDefaultHeight(widthMeasureSpec, heightMeasureSpec);
        }
        setMeasuredDimension(finalWidth, finalHeight);
    }

    private int getDefaultWidth(int widthMeasureSpec, int heightMeasureSpec) {
        //child with
        int count = getChildCount();
        int childMaxWidth = 0;
        int childState = 0;
        for (int i = 0; i < count; i++) {
            final View child = getChildAt(i);
            if (child.getVisibility() != GONE) {
                measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
                final LayoutParams lp = (LayoutParams) child.getLayoutParams();
                childMaxWidth = Math.max(childMaxWidth,
                        child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
                childState = combineMeasuredStates(childState, child.getMeasuredState());
            }
        }

        //contents
        int contentWidth = 0;
        if (null != mDrawableStart) {
            contentWidth += mDrawableStartWidth;
            contentWidth += mDrawablePadding;
        }
        contentWidth += getTextWidth(getText(), mTextSize);
        contentWidth += getTextWidth(getTextEnd(), mTextSizeEnd);
        if (null != mDrawableEnd) {
            contentWidth += mDrawableEndWidth;
            contentWidth += mDrawablePadding;
        }

        // compare child and contents use big one
        int maxWidth = Math.max(childMaxWidth, contentWidth);
        //padding
        maxWidth += getPaddingStart() + getPaddingEnd();
        return maxWidth;
    }

    private int getDefaultHeight(int widthMeasureSpec, int heightMeasureSpec) {
        //child height
        int count = getChildCount();
        int childMaxHeight = 0;
        int childState = 0;
        for (int i = 0; i < count; i++) {
            final View child = getChildAt(i);
            if (child.getVisibility() != GONE) {
                measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
                final LayoutParams lp = (LayoutParams) child.getLayoutParams();
                childMaxHeight = Math.max(childMaxHeight,
                        child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
                childState = combineMeasuredStates(childState, child.getMeasuredState());
            }
        }

        //drawable height
        int drawableHeight = mDrawableStartHeight > mDrawableEndHeight ? mDrawableStartHeight : mDrawableEndHeight;
        //texts height
        int textHeight = 0;
        TextPaint textPaint = new TextPaint();
        textPaint.setTextSize(mTextSize);
        List<DrawTextInfo> baseLineInfo = getDrawTextInfoBase(getText(), getTextRange(), textPaint);
        if (baseLineInfo.size() > 0) {
            textHeight = (baseLineInfo.size()) * (getTextHeight(mTextSize));
            textHeight += (baseLineInfo.size() - 1) * mLineSpaceExtra;
            int textHeightEnd = getTextHeight(mTextSizeEnd);
            textHeight = textHeight > textHeightEnd ? textHeight : textHeightEnd;
        }
        // compare drawable height and text height , use the big one to be content Height
        int contentHeight = drawableHeight > textHeight ? drawableHeight : textHeight;
        // compare content height and child height , use the big one
        int maxHeight = Math.max(childMaxHeight, contentHeight);

        //padding
        maxHeight += getPaddingTop() + getPaddingBottom();
        return maxHeight;
    }

    private int getTextRectHeight(CharSequence text, float textSize) {
        if (TextUtils.isEmpty(text)) {
            return 0;
        }
        Rect rect = new Rect();
        TextPaint paint = new TextPaint();
        paint.setTextSize(textSize);
        paint.getTextBounds(text.toString(), 0, text.length(), rect);
        return rect.height();
    }

    private int getTextHeight(float textSize) {
        TextPaint textPaint = new TextPaint();
        textPaint.setTextSize(textSize);
        Paint.FontMetricsInt fm = textPaint.getFontMetricsInt();
        return -(fm.bottom + fm.top);
    }

    private int getTextWidth(CharSequence text, float textSize) {
        if (TextUtils.isEmpty(text)) {
            return 0;
        }
        TextPaint paint = new TextPaint();
        paint.setTextSize(textSize);
        return (int) paint.measureText(text.toString());
    }

    private int getDrawYOffSet() {
        int paddingTop = getPaddingTop();
        int paddingBottom = getPaddingBottom();
        if (paddingTop == paddingBottom) {
            return 0;
        }
        return (paddingTop - paddingBottom) / 2;
    }

    private void initTextPaint() {
        if (null == mTextPaint) {
            mTextPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
            mTextPaint.density = getResources().getDisplayMetrics().density;
        }
    }

    private int getTextRange() {
        int width = getMeasuredWidth();
        int startX = getPaddingStart() + (mDrawableStart == null ? 0 : mDrawableStartWidth + mDrawablePadding);
        int endX = width - getPaddingEnd() - (mDrawableEnd == null ? 0 : mDrawableEndWidth + mDrawablePadding) - (TextUtils.isEmpty(mTextEnd) ? 0 : getTextWidth(mTextEnd, mTextSizeEnd));
        return endX - startX;
    }

    private void drawCellText(Canvas canvas) {
        mTextPaint.setTextSize(mTextSize);
        mTextPaint.setColor(mTextColor);
        mTextPaint.setFakeBoldText(mIsTextBold);
        int startX = getPaddingStart() + (mDrawableStart == null ? 0 : mDrawableStartWidth + mDrawablePadding);
        int range = getTextRange();
        List<DrawTextInfo> allLineText = getDrawTextInfoToDraw(getText(), range, startX, mTextPaint);
        for (DrawTextInfo info : allLineText) {
            drawText(canvas, info.drawX, info.drawY, info.text.toString(), range, mTextPaint);
        }
    }

    private void drawCellTextEnd(Canvas canvas) {
        mTextPaint.setTextSize(mTextSizeEnd);
        mTextPaint.setColor(mTextColorEnd);
        mTextPaint.setFakeBoldText(mIsTextBold);
        int drawTextX = getWidth() - getPaddingEnd() - mDrawablePadding - (mDrawableEnd == null ? 0 : mDrawableEndWidth) - (int) mTextPaint.measureText(getTextEnd());
        Paint.FontMetricsInt fm = mTextPaint.getFontMetricsInt();
        canvas.drawText(getTextEnd(), drawTextX, getHeight() / 2 - (fm.bottom + fm.top) / 2 + getDrawYOffSet(), mTextPaint);
    }

    private void drawText(Canvas canvas, float x, float y, String text, int range, TextPaint paint) {
        float textWidth = paint.measureText(text);
        if (isTextLengthBeyondRange(text, range, paint)) {
            float sizeOffset = range / textWidth;
            int subLength = (int) (text.length() * sizeOffset);
            text = text.substring(0, subLength - 1);
            text += "...";
        }
        canvas.drawText(text, x, y, paint);
    }

    private List<DrawTextInfo> getDrawTextInfoToDraw(String textTobeDraw, int horizontalRange, int drawStartX, TextPaint textPaint) {
        List<DrawTextInfo> allLineText = getDrawTextInfoBase(textTobeDraw, horizontalRange, textPaint);
        if (allLineText.size() == 0) {
            return allLineText;
        }
        Paint.FontMetricsInt fm = textPaint.getFontMetricsInt();
        int textHeight = -(fm.bottom + fm.top);
        int totalTextHeight = allLineText.size() * textHeight;
        totalTextHeight += (allLineText.size() - 1) * mLineSpaceExtra;
        int drawStartY = (getHeight() - totalTextHeight) / 2 + getDrawYOffSet();
        for (DrawTextInfo info : allLineText) {
            drawStartY += textHeight;
            info.setLocation(drawStartX, drawStartY);
            drawStartY += mLineSpaceExtra;
        }
        return allLineText;
    }

    private List<DrawTextInfo> getDrawTextInfoBase(String textTobeDraw, int horizontalRange, TextPaint textPaint) {
        int remainLineCount = mMaxLines;
        List<DrawTextInfo> allLineText = new LinkedList<>();
        // if has "/n" need warp
        while (textTobeDraw.contains(WRAP)) {
            int wrapIndex = textTobeDraw.indexOf(WRAP);
            if (wrapIndex != -1) {
                String thisLine = textTobeDraw.substring(0, wrapIndex);
                // if this subLine's length beyond the horizontal range ，it need auto wrap
                while (isTextLengthBeyondRange(thisLine, horizontalRange, textPaint) && remainLineCount > 1) {
                    float textWidth = textPaint.measureText(thisLine);
                    float sizeOffset = horizontalRange / textWidth;
                    int subLengthIndex = (int) (thisLine.length() * sizeOffset);
                    if (subLengthIndex > 0) {
                        remainLineCount--;
                        allLineText.add(new DrawTextInfo(thisLine.substring(0, subLengthIndex)));
                        thisLine = textTobeDraw.substring(subLengthIndex, textTobeDraw.length());
                    } else {
                        break;
                    }
                }

                if (remainLineCount > 0) {
                    remainLineCount--;
                    allLineText.add(new DrawTextInfo(thisLine));
                    textTobeDraw = textTobeDraw
                            .substring(wrapIndex, textTobeDraw.length())
                            .replaceFirst(WRAP, "");
                } else {
                    break;
                }
            } else {
                break;
            }
        }
        // if singleLine without wrap and beyond range
        while (isTextLengthBeyondRange(textTobeDraw, horizontalRange, textPaint) && remainLineCount > 1) {
            float textWidth = textPaint.measureText(textTobeDraw);
            float sizeOffset = horizontalRange / textWidth;
            int subLengthIndex = (int) (textTobeDraw.length() * sizeOffset);
            if (subLengthIndex > 0) {
                remainLineCount--;
                allLineText.add(new DrawTextInfo(textTobeDraw.substring(0, subLengthIndex)));
                textTobeDraw = textTobeDraw.substring(subLengthIndex, textTobeDraw.length());
            } else {
                break;
            }
        }
        //final
        if (remainLineCount > 0) {
            allLineText.add(new DrawTextInfo(textTobeDraw));
        }
        return allLineText;
    }

    private boolean isTextLengthBeyondRange(String text, int range, TextPaint paint) {
        return range != 0 && paint.measureText(text) > range;
    }

    private void drawAllDrawables(Canvas canvas) {
        drawStart(canvas);
        drawEnd(canvas);
    }

    private void drawDividers(Canvas canvas) {
        if (null == mDivider) {
            return;
        }
        if (mShowDividers == SHOW_DIVIDER_NONE) {
            return;
        }
        int boundLeft;
        int boundRight;
        boolean showDividerTop = (mShowDividers & SHOW_DIVIDER_TOP) == SHOW_DIVIDER_TOP;
        if (showDividerTop) {
            boundLeft = mDividerPaddingGravity == DIVIDER_PADDING_GRAVITY_END ? 0 : mDividerTopPadding;
            boundRight = getWidth() - (mDividerPaddingGravity == DIVIDER_PADDING_GRAVITY_START ? 0 : mDividerTopPadding);
            mDivider.setBounds(boundLeft, 0, boundRight, mDividerHeight);
            drawDrawable(canvas, mDivider, 0, 0);
        }
        boolean showDividerBottom = (mShowDividers & SHOW_DIVIDER_BOTTOM) == SHOW_DIVIDER_BOTTOM;
        if (showDividerBottom) {
            boundLeft = mDividerPaddingGravity == DIVIDER_PADDING_GRAVITY_END ? 0 : mDividerBottomPadding;
            boundRight = getWidth() - (mDividerPaddingGravity == DIVIDER_PADDING_GRAVITY_START ? 0 : mDividerBottomPadding);
            mDivider.setBounds(boundLeft, 0, boundRight, mDividerHeight);
            drawDrawable(canvas, mDivider, 0, getHeight() - mDividerHeight);
        }
    }

    private void drawStart(Canvas canvas) {
        if (null == mDrawableStart) {
            return;
        }
        int finalWidth = mDrawableStartWidth <= 0 ? mDrawableStart.getIntrinsicWidth() : mDrawableStartWidth;
        int finalHeight = mDrawableStartHeight <= 0 ? mDrawableStart.getIntrinsicHeight() : mDrawableStartHeight;
        mDrawableStart.setBounds(0, 0, finalWidth, finalHeight);
        drawDrawable(canvas, mDrawableStart, getPaddingStart(), getMeasuredHeight() / 2 - finalHeight / 2 + getDrawYOffSet());
        mDrawableStartWidth = finalWidth;
        mDrawableStartHeight = finalHeight;
    }

    private void drawEnd(Canvas canvas) {
        if (null == mDrawableEnd) {
            return;
        }
        int finalWidth = mDrawableEndWidth <= 0 ? mDrawableEnd.getIntrinsicWidth() : mDrawableEndWidth;
        int finalHeight = mDrawableEndHeight <= 0 ? mDrawableEnd.getIntrinsicHeight() : mDrawableEndHeight;
        mDrawableEnd.setBounds(0, 0, finalWidth, finalHeight);
        drawDrawable(canvas, mDrawableEnd, getMeasuredWidth() - getPaddingEnd() - mDrawableEndWidth, getMeasuredHeight() / 2 - finalHeight / 2 + getDrawYOffSet());
        mDrawableEndWidth = finalWidth;
        mDrawableEndHeight = finalHeight;
    }

    private void drawDrawable(Canvas canvas, Drawable drawable, int x, int y) {
        canvas.save();
        canvas.translate(x, y);
        drawable.draw(canvas);
        canvas.restore();
    }

    private class DrawTextInfo {
        int drawX;
        int drawY;
        CharSequence text;

        DrawTextInfo(CharSequence text) {
            this.text = text;
        }

        void setLocation(int drawX, int drawY) {
            this.drawX = drawX;
            this.drawY = drawY;
        }
    }
}
